/*
 * Created on Nov 23, 2003
 */
package com.unash.util;

import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
/**
 * @author Ashwini Gokhale
 **/

public class AsnUtil implements Consts {
	private AsnUtil() {
	} //AsnUtil

	public static class TagInfo {
		public int tag;
		public byte tagClass;
		public boolean isConstructed;

		public TagInfo() {
		} //TagInfo

		public TagInfo(int tag, byte tagClass, boolean isConstructed) {
			this.tag = tag;
			this.tagClass = tagClass;
			this.isConstructed = isConstructed;
		} //TagInfo(int tag,byte tagClass,boolean isConstructed)
	} //TagInfo

	/**
	 * This function reads length field from the Ber encoded input stream.
	 * It is assumed that the legnth is not more than an int can represent.
	 * Also the current byte in the inputstream is first byte of the length field. 
	 * @param bs Ber encoded stream
	 * @return Length of the field or 0 if length is indefinite
	 **/
	public static int decodeLength(InputStream bs) throws Exception {
		byte length = readByte(bs);

		//Length is Indefinite
		if (length == ZERO)
			return ZERO;

		if (isLongLength(length)) {
			length = maskZero(length, FIRST_BIT);
			ByteArray longLength = new ByteArray(bs, length);
			System.out.println("Length is " + length);
			//return (Integer.parseInt(longLength.toString(),BINARY));
			return new BigInteger(longLength.toString(), BINARY).intValue();

		} //if
		else {
			return length;
		} //else
	} //getLength

	public static byte[] encodeLength(int value) {
		byte[] finalLength = null;
		if (value > MAX_SINGLE_BYTE_VALUE) {
			byte[] len = intToBerByteArray(value);
			finalLength = new byte[len.length + ONE];
			finalLength[ZERO] = (byte) len.length;
			System.arraycopy(len, ZERO, finalLength, ONE, len.length);
		} else {
			finalLength = new byte[ONE];
			finalLength[ZERO] = (byte) value;
		} //else

		return finalLength;
	} //encodeLength(int length)

	/**
	 * This function reads tag field from the Ber encoded input stream.
	 * It is assumed that the current byte in the inputstream is first 
	 * byte of the tag field.
	 * @param bs Ber Encoded Stream
	 * @return TagInfo structure which includes Tag, TagClass, isConstructed info.
	 **/
	public static TagInfo decodeTag(InputStream bs) throws Exception {
		try {
			byte leadTag = readByte(bs);

			TagInfo tagInfo = new TagInfo();
			tagInfo.tagClass = decodeTagClass(leadTag);
			tagInfo.isConstructed = isBitSet(leadTag, IS_CONSTRUCTED_BIT);
			//Tagnumber contains more than one octet[bits 5 to 1 encoded as 11111]
			if (ZERO == ((~leadTag) & (MASK_LEADING_THREE_BITS))) {
				tagInfo.tag = ZERO;
				while (true) {
					byte tag = readByte(bs);
					tagInfo.tag = (tag & MASK_LEADING_ONE_BIT) | tagInfo.tag;
					tagInfo.tag = tagInfo.tag << 7;
					//bit 8 of last octet is 0
					if (!(isBitSet(tag, FIRST_BIT)))
						break;
				} //(true)
				tagInfo.tag = tagInfo.tag >> 7;
			} //(~leadTag == 0)
			else {
				tagInfo.tag = (int) (leadTag & MASK_LEADING_THREE_BITS);
			} //else

			return tagInfo;
		} catch (Exception e) {
			e.printStackTrace();
			throw e;
		} catch (Throwable t) {
			t.printStackTrace();
			return null;
		}

	} //getTag(InputStream bs,int tagClass,int tagNumber)

	public static byte[] encodeTag(
		int tag,
		byte tagClass,
		boolean isConstructed)
		throws Exception {
		byte[] finalTag = null;
		byte leadTag = ZERO;
		leadTag = (byte) (tagClass << 6);
		if (isConstructed)
			maskOne(leadTag, IS_CONSTRUCTED_BIT);

		if (tag < MASK_LEADING_THREE_BITS) {
			finalTag = new byte[ONE];
			finalTag[ZERO] = (byte) (leadTag | tag);
		} else {
			byte[] tagValue = intToBerByteArray(tag);
			finalTag = new byte[tagValue.length + ONE];
			finalTag[ZERO] = (byte) (leadTag | MASK_LEADING_THREE_BITS);
			System.arraycopy(tagValue, ZERO, finalTag, ONE, tagValue.length);
		} //else
		return finalTag;
	} //setTag(OutputStream os,TagInfo tagInfo)

	/**
	 * This function read the bits 8 and 7 [Mask the bits from 6-1] 
	 * and find the class of the tag   
	 * @param leadTag
	 * @return the integer value of the tag Class.
	 */
	private static byte decodeTagClass(byte leadTag) {
		for (int i = TAG_CLASS_LENGTH; i <= BYTE_SIZE; i++)
			leadTag = maskZero(leadTag, i);
		return leadTag;
	} //getClass(Byte leadTad)

	/**
	 * This function checks bit 8, if it is set then the length is Long Definite.
	 * @param : First byte of the length field.
	 * @return
	 **/
	private static boolean isLongLength(byte length) {
		if (isBitSet(length, FIRST_BIT))
			return true;
		return false;
	} //isLongLength

	/**
	 * This function is used to mask the specifed bit[index] with 0.
	 * @param value
	 * @param index [1-8]
	 * @return
	 */
	public static byte maskZero(byte value, int index) {
		int lShiftValue = MASK << (BYTE_SIZE - index);
		lShiftValue = ~lShiftValue;
		byte result = (byte) (lShiftValue & value);
		return result;
	} //maskZero(byte value, int index)

	public static byte maskOne(byte value, int index) {
		int lShiftValue = MASK << (BYTE_SIZE - index);
		byte result = (byte) (lShiftValue | value);
		return result;
	} //maskOne(byte value,int index)

	/**
	 * This function reads specified number of bytes from the InputStream.
	 * @param bs InputStream
	 * @param noBytes to be read
	 * @return byte array
	 * @throws IOException
	 */
	public static byte[] readNBytes(InputStream bs, int noBytes)
		throws IOException {
		byte[] value = new byte[noBytes];
		int rc = ZERO;
		int offset = ZERO;
		int cnt = noBytes;
		while (ZERO < cnt) {
			rc = bs.read(value, offset, cnt);
			if (ZERO >= rc) {
				throw new IOException("Error reading data.");
			} //if(0 > rc)
			offset += rc;
			cnt -= rc;
		} //while(cnt > 0)

		return value;
	} //readNBytes

	/**
	 * This function reads a single byte from the InputStream.
	 * @param bs InputStream
	 * @return byte
	 * @throws IOException
	 */
	public static byte readByte(InputStream bs) throws IOException {
		try {
			byte value = (byte) (bs.read());
			if (value == END_OF_STREAM)
				throw new IOException("Error reading data.");

			return value;
		} catch (IOException e) {
			e.printStackTrace();
			throw e;
		}
	} //readByte(InputStream bs)

	/**
	 * This function checks whether the specified bit is set to 1.
	 * @param index ranging from 1 to 8.
	 * @return true if bit = 1
	 * @throws IllegalArgumentException
	 **/
	public static boolean isBitSet(byte value, int index)
		throws IllegalArgumentException {
		if ((ZERO >= index))
			throw new IllegalArgumentException();
		int noBitRShift = BYTE_SIZE - index;
		int rShiftValue = value >> noBitRShift;
		return ((rShiftValue & MASK) == ONE);
	} //isBitSet(int index)

	/**
	 * This function returns the first nobytes from the byte array[in] 
	 * @param in
	 * @param nobytes
	 * @return
	 */
	public static byte[] getLeadingBytes(byte[] in, int noOfBytes) {
		byte[] result = new byte[noOfBytes];
		for (int i = ZERO; i < noOfBytes; i++) {
			result[i] = in[i];
		} //for (int i = 0; i < noOfBytes; i++)
		return result;
	} //getLeadingBytes(byte[]in, int noOfBytes)

	/**
	 * This function returns last nobytes from the byte array[in]. 
	 * @param in
	 * @param noOfBytes
	 * @return
	 */
	public static byte[] getTrailingBytes(byte[] in, int noOfBytes) {
		byte[] result = new byte[noOfBytes];
		int cnt = ZERO;
		for (int i = (in.length - noOfBytes); i < (in.length); i++) {
			result[cnt++] = in[i];
		} //(i=(in.length-noOfBytes),i<in.length;i++)
		return result;
	} //getTrailingBytes()

	public static byte[] intToBerByteArray(int value) {

		byte[] resultRev = new byte[10];
		int pos = ZERO;
		while (0x7F < Math.abs(value)) {
			resultRev[pos] = (byte) (value & 0x7F);
			value = value >> 7;
			pos++;
		} //while

		resultRev[pos] = (byte) value;
		byte[] result = new byte[pos + ONE];
		int j = pos;
		for (int i = ZERO; i <= pos; i++) {
			result[i] = resultRev[j--];
			int cast = (i == pos ? 0x0 : 0x80);
			result[i] = (byte) (result[i] | cast);
		}

		return result;
	} //intToByte(int value)	

} //AsnUtil